package scenetracer.core.primitive;

import scenetracer.math.Matrix4;
import scenetracer.math.Point2;
import scenetracer.math.Point3;
import scenetracer.math.Vector3;

public class Triangle implements Cloneable{

	public Vector3 normalVector;
	public Point3 [] points;
	public float epsilon = 0.000001F;
	public Vector3 e1;
	public Vector3 e2;
	public Point3 [] normalPoints;
	public float s;
	public float t;
	private boolean interpolate;
	public boolean UVSet;
	public float [] textureU;
	public float [] textureV;
	
	public Triangle(Point3 a, Point3 b, Point3 c, Point3 na, Point3 nb, Point3 nc, float[] u, float [] v)
	{
		this.points = new Point3[3];
		this.points[0] = a;
		this.points[1] = b;
		this.points[2] = c;
		
		computeVectors();
		
		//Almacenamos las normales
		this.normalPoints = new Point3[3];
		this.normalPoints[0] = na;
		this.normalPoints[1] = nb;
		this.normalPoints[2] = nc;
		
//		Almacenamos los uvs
		this.textureU = u;
		this.textureV = v;
		
		this.interpolate = true;
		this.UVSet = true;
	}
	
	public Triangle (Point3 a, Point3 b, Point3 c, float[] u, float [] v)
	{
		this.points = new Point3[3];
		this.points[0] = a;
		this.points[1] = b;
		this.points[2] = c;
		
		computeVectors();
		computeNormalVector();

//		Almacenamos los uvs
		this.textureU = u;
		this.textureV = v;
		
		this.interpolate = false;
		this.UVSet = true;
	}
	
	public Triangle (Point3 a, Point3 b, Point3 c, Point3 na, Point3 nb, Point3 nc)
	{
		this.points = new Point3[3];
		this.points[0] = a;
		this.points[1] = b;
		this.points[2] = c;
		
		computeVectors();
		
		//Almacenamos las normales
		this.normalPoints = new Point3[3];
		this.normalPoints[0] = na;
		this.normalPoints[1] = nb;
		this.normalPoints[2] = nc;
		
		this.interpolate = true;
		this.UVSet = false;
	}
	
	public Triangle (Point3 a, Point3 b, Point3 c)
	{
		this.points = new Point3[3];
		this.points[0] = a;
		this.points[1] = b;
		this.points[2] = c;
		
		computeVectors();
		computeNormalVector();
		
		this.interpolate = false;
		this.UVSet = false;
	}
	
	public Triangle clone()
	{
		Point3 a = new Point3(this.points[0]);
		Point3 b = new Point3(this.points[1]);
		Point3 c = new Point3(this.points[2]);
		
		return new Triangle( a, b, c);
	}

	protected void transform(Matrix4 transform)
	{
		if (transform == null) {
			throw new IllegalArgumentException();
		}
		
		points[0] = transform.transformP(points[0]);
		points[1] = transform.transformP(points[1]);
		points[2] = transform.transformP(points[2]);
		
		
		computeVectors();
		computeNormalVector();
	}
	
	
	public Point3[] getPoints() {
		return points;
	}
	
	public Point3 findIntersectionWithLine(Point3 origin, Vector3 orientation)
	{
		Vector3 p = Vector3.cross( orientation, e2);
		
		float det = Vector3.dot(e1, p);
		
		if( det > -epsilon && det < epsilon ) {
			return null;
		}
		
		Vector3 tBig = Vector3.sub( origin, points[0] );
		
		Vector3 q = Vector3.cross( tBig, e1);
		
		float t, u, v;

		Vector3 tmp = new Vector3( Vector3.dot(q, e2 ), Vector3.dot(p, tBig), Vector3.dot(q, orientation ));
		tmp.mul( 1 / ( Vector3.dot(p, e1 ) ) );
		
		t = tmp.x;
		u = tmp.y;
		v = tmp.z;
		
		if( t < 0 ) {
			return null;
		}
		
		if( u < 0 || u > 1 ) {
			return null;
		}
		
		if( v < 0 || (v+u) > 1 ) {
			return null;
		}

		this.s = u;
		this.t = v;
		return new Point3( origin.x + orientation.x * t, 
				origin.y + orientation.y * t,
				origin.z + orientation.z * t );
	}
	
	public float[] getUV(Point3 origin, Vector3 orientation)
	{

		Vector3 e1 = Vector3.sub( points[1], points[0] );
		
		Vector3 e2 = Vector3.sub( points[2], points[0] );
		
		Vector3 tBig = Vector3.sub( origin, points[0] );
		
		Vector3 q = Vector3.cross( tBig, e1);
		
		Vector3 p = Vector3.cross( orientation, e2);
		
		float det = Vector3.dot(e1, p);
		float t, u, v;
		
		if( det > -epsilon && det < epsilon ) {
			return null;
		}
		
		Vector3 tmp = new Vector3( Vector3.dot(q, e2 ), Vector3.dot(p, tBig), Vector3.dot(q, orientation ));
		tmp.mul( 1 / ( Vector3.dot(p, e1 ) ) );
		
		t = tmp.x;
		u = tmp.y;
		v = tmp.z; 
		
		if( t < 0 ) {
			return null;
		}
		
		if( u < 0 || u > 1 ) {
			return null;
		}
		
		if( v < 0 || (v+u) > 1 ) {
			return null;
		}
		
		float[] res = new float[2];
		res[0] = new Float(u);
		res[1] = new Float(v);
		return res;
	}
	
	public Vector3 getNormal(Point3 p) {
		if (!interpolate)
			return normalVector;
		
		float w = 1.0f - s - t;
		Vector3 tmp = new Vector3 ( w * normalPoints[0].x + s * normalPoints[1].x + t * normalPoints[2].x,
				w * normalPoints[0].y + s * normalPoints[1].y + t * normalPoints[2].y,
				w * normalPoints[0].z + s * normalPoints[1].z + t * normalPoints[2].z);
		tmp.normalize();
		return tmp;
	}
	
	private void computeNormalVector() {
	
		normalVector = Vector3.cross(e1, e2);
		normalVector.normalize();
	}
	
	private void computeVectors() {
		e1 = Vector3.sub( points[1], points[0] );
		e2 = Vector3.sub( points[2], points[0] );
		
	}
	
	public Point2 getTextureCoords(Point3 point) {
		Plane plane = new Plane(new Point3(0, 0, 0), normalVector);
		return plane.getTextureCoords(point);
	}

	/*
	@Override
	public boolean Intersect(Ray ray, IntersectionState intersection) {
		Vector3    u, v, n;            
	    Vector3    dir, w0, w;         
	    float     r, a, b;             

	    // Edge vectors and plane normal
	    
	    u = Point3.sub(this.points[1], this.points[0]);	    
	    v = Point3.sub(this.points[2], this.points[0]);
	    n = Vector3.cross(u, v, new Vector3());
	    
	    
	    if (n.x == 0 && n.y == 0 && n.z == 0)            // triangle is degenerate
	    	return false;

	    dir = ray.getDirection();
	    w0 = Point3.sub(ray.getOrigin(), this.points[0]);
	    

	    a = -Vector3.dot(n, w0);
	    b = Vector3.dot(n, dir);
	    
	    if (Math.abs(b) < 0.000000000001) {     // ray is parallel to triangle plane
	    	return false;
	    }

	    // get intersect point of ray with triangle plane
	    r = a / b;
	    if (r < 0.0)                   // ray goes away from triangle
	    	return false;


	    // intersect point of ray and plane
	    Point3 intersectionPoint = Point3.add(ray.getOrigin(), 
	    		new Vector3(ray.getDirection()).mul(r), new Point3());
	    
	    // is intersectionPoint inside this triangle?
	    float    uu, uv, vv, wu, wv, D;
	    uu = Vector3.dot(u,u);
	    uv = Vector3.dot(u,v);
	    vv = Vector3.dot(v,v);
	    w = Point3.sub(intersectionPoint, this.points[0]);
	    wu = Vector3.dot(w,u);
	    wv = Vector3.dot(w,v);
	    D = uv * uv - uu * vv;

	    float s, t;
	    s = (uv * wv - vv * wu) / D;
	    if (s < 0.0 || s > 1.0){
	    	return false;
	    }
	    t = (uv * wu - uu * wv) / D;
	    if (t < 0.0 || (s + t) > 1.0){ 
	    	return false;
	    }

	    intersection.addPoint(intersectionPoint);
	    intersection.setIntersectionKind(IntersectionKind.OUT);
		intersection.setNormal(getNormal(intersection));
	    return true;

	}

	@Override
	public Vector3 getNormal(IntersectionState intersection) {
		return normalVector;
	}
	*/
}
